#include "window.hpp"
#include <stdlib.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>

#include "matrix.cpp"
#include "vector.cpp"

#include <iostream>
using namespace std;

void bind_texture() {
	glBindTexture(GL_TEXTURE_2D, tex_name);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texture_width, texture_height, 0, GL_RGB, GL_UNSIGNED_BYTE, texture_buffer);
}


template <class t> void SWAP(t &a, t &b) {
	t temp = a;
	a = b;
	b = temp;
}
#define ABS(a) ((a) < 0 ? -(a) : (a))
#define MIN(a, b) ((a)<(b)?(a):(b))
#define MAX(a, b) ((a)>(b)?(a):(b))

void drawpixel(vector<int> point, vector<double> weights, float intensity, vector<int> t0, vector<int> t1, vector<int> t2) {
	int tx = (int)(weights.dot(vector<double>(t0.x, t1.x, t2.x)));
	int ty = (int)(weights.dot(vector<double>(t0.y, t1.y, t2.y)));
	if (point.z<zbuffer[point.x+point.y*texture_width]) {
		zbuffer[point.x+point.y*texture_width] = point.z;
		TGAColor color = tgaimage.get(tx, ty);
		color.r *= intensity;
		color.g *= intensity;
		color.b *= intensity;
		screen.set(point.x, point.y, color);
	}
}

void bilinear(vector<int> p0, vector<int> p1, vector<int> p2, float intensity, vector<int> t0, vector<int> t1, vector<int> t2) {
	if (!zbuffer) return;
	if (p0.y==p1.y && p0.y==p2.y) return;
	if (p0.y>p1.y) {     // p0.x,p0.y will be the top point
		SWAP(p0, p1);    // p1.x,p1.y will be the lower (say "left")  (p1.y may be = to p0.y)
		SWAP(t0, t1);    // p2.x,p2.y will be the lower (say "right") (p2.y may be = to p0.y)
	}
	if (p0.y>p2.y) {
		SWAP(p0, p2);
		SWAP(t0, t2);
	}
	if (p1.y>p2.y) {
		SWAP(p1, p2);
		SWAP(t1, t2);
	}
	vector<double> v1(0,0,0);
	vector<double> v2(0,0,0);
	for (int i=0; i<p2.y-p0.y; i++) {
		v2.z = (double)i/(double)(p2.y-p0.y);  // in fact, p2.y-p0.y is the height of the triangle
		v2.x = 1-v2.z;
		if (i>=p1.y-p0.y) {
			v1.x = 0;
			v1.z = (double)(i-(p1.y-p0.y))/(double)(p2.y-p1.y);
			v1.y = 1-v1.z;
		} else {
			v1.y = (double)i/(double)(p1.y-p0.y);
			v1.x = 1-v1.y;
			v1.z = 0;
		}
		int b1 = (int)(v1.dot(vector<double>(p0.x,p1.x,p2.x)));
		int b2 = (int)(v2.dot(vector<double>(p0.x,p1.x,p2.x)));
		int linesize = ABS(b1-b2)+1;
		for (int t=0; t<linesize; t++) {
			vector<double> weights = v1 + (v2-v1)*(float)t/(float)linesize;
			vector<int> point(MIN(b1,b2)+t, i+p0.y, (int)(weights.dot(vector<double>(p0.z, p1.z, p2.z))));
			drawpixel(point, weights, intensity, t0, t1, t2);
		}
	}
}


vector<int> device2screen(vector<double> p) {
	vector<double> res = (p+vector<double>(1.0,1.0,1.0))^vector<double>(texture_width, texture_height, texture_depth)/2.0;
	return (vector<int>(res.x, res.y, res.z));
}

vector<int> model2image(vector<double> p) {
	vector<double> res = (p)^vector<double>(tgaimage.get_width(), tgaimage.get_height(), 1);
	return (vector<int>(res.x, res.y, res.z));
}


void recalc_texture() {
	if (!texture_buffer||!zbuffer) return;
	for (int i=0; i<texture_width*texture_height; i++) {
		zbuffer[i] = 1000.0;
	}
	screen = TGAImage(texture_width, texture_width, TGAImage::RGB);

	vector<int> p0, p1, p2;
	vector<int> t0, t1, t2;
	vector<double> normal;
	float intensity = 1.0;
	for(mt->beginNode();mt->hasNextNode();mt->nextNode()){
		normal = mt->getNormal();
		intensity = ABS(normal.dot(light0)/normal.length());
		p0 = device2screen(mt->beginPointByNode());
		p1 = device2screen(mt->nextPointByNode());
		p2 = device2screen(mt->nextPointByNode());
		t0 = model2image(mt->beginTexByNode());
		t1 = model2image(mt->nextTexByNode());
		t2 = model2image(mt->nextTexByNode());
		bilinear(p0, p1, p2, intensity, t0, t1, t2);
	}
	screen.write_tga_file("dump.tga");

	for (int i=0; i<texture_width; i++) {
		for (int j=0; j<texture_height; j++) {
			TGAColor c = screen.get(i,j);
			texture_buffer[(i+j*texture_width)*3] = c.r;
			texture_buffer[(i+j*texture_width)*3+1] = c.g;
			texture_buffer[(i+j*texture_width)*3+2] = c.b;
		}
	}
}

void init(){
	width = 500;
	height = 500;
	fps = 20;
	normaldir = true;
	redraw = true;
	//initialisation du modèle taureau et affichage
	//m = model::importFromFile("./data/taureau.txt");
	mt = modelTextured::importFromFile("./data/head.txt");

	//Aggrandissement du modèle pasque bon voila
	double size = 200.;
	vector<double> v = vector<double>(size,size,size);

	Matrix<double> matrice = Matrix<double>::MatrixScale(v);
	//m->addMatrix(matrice);
	//mt->addMatrix(matrice);

	//initialisation d'une arcball
	vector<int> c;
	a = new arcball(c, width/2,mt);
	//a2 = new arcball(c, width/2,(model*) m2);

	//Chargement texture

	glClearColor (0.0, 0.0, 0.0, 0.0);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glGenTextures(1, &tex_name);
	texture_buffer = new unsigned char[texture_width*texture_height*3];
	tgaimage.read_tga_file("./data/head.tga");
	tgaimage.flip_vertically();
	recalc_texture();
	bind_texture();

	//Etat souris
	mouseLeft = mouseRight = mouseMiddle = false;
	if (zbuffer) delete [] zbuffer;
	zbuffer = new float[texture_width*texture_height];
	light0.normalize();
}

void destroy(){
	//delete a1;
	//delete a2;
	//delete m;
	delete mt;
	if (zbuffer) delete [] zbuffer;
}

void launch(int argc, char **argv){

	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_DEPTH | GLUT_SINGLE | GLUT_RGB);
	//glutInitWindowPosition(100,100);
	glutInitWindowSize(width,height);
	win = glutCreateWindow("Test");
	glClearColor(0.f,0.f,0.f,0.f);

	glEnable(GL_TEXTURE_2D);
	glViewport (-width/2, -width/2, width, width);
	gluLookAt(0,0,0,0,0,-1,0,1,0);

    // parametrage de la fonction d'affichage (obligatoire)
    glutDisplayFunc(draw);
	glutKeyboardFunc(keyboard);
	glutMouseFunc(mouse);
	glutMotionFunc(motion);
	glutIdleFunc(wait);

    // boucle principale de GLUT
    glutMainLoop();
}


void wait(){
	static int nWaitUntil = glutGet(GLUT_ELAPSED_TIME);
	int nTimer = glutGet(GLUT_ELAPSED_TIME);
	if(nTimer >= nWaitUntil){
		if(redraw){
			glutPostRedisplay();
			redraw = false;
		}
		nWaitUntil = nTimer + (1000 / fps);
	}
}

void keyboard(unsigned char touche, int x, int y){
	if(touche=='q' || touche == 27){
		glutDestroyWindow(win);
		return;
	}
	if(touche=='a'){	//Diminution de la taille du modèle de 10%
		double size = 0.9;
		vector<double> v = vector<double>(size,size,size);
		Matrix<double> matrice = Matrix<double>::MatrixScale(v);
		mt->addMatrix(matrice);
		redraw = true;
		return;
	}
	if(touche=='e'){ 	//Augmentation de la taille du modèle de 10%
		double size = 1.1;
		vector<double> v = vector<double>(size,size,size);
		Matrix<double> matrice = Matrix<double>::MatrixScale(v);
		mt->addMatrix(matrice);
		redraw = true;
		return;
	}
	if(touche=='s'){ 	//Augmentation de la taille du modèle de 10%
		double size = 100.;
		vector<double> v = vector<double>(size,0,0);
		Matrix<double> matrice = Matrix<double>::MatrixTranslation(v.x,1.,1.);
		mt->addMatrix(matrice);
		redraw = true;
		return;
	}
	if(touche=='d'){ 	//Augmentation de la taille du modèle de 10%
		double size = 100.;
		vector<double> v = vector<double>(size,0,0);
		Matrix<double> matrice = Matrix<double>::MatrixTranslation(-v.x,1.,1.);
		mt->addMatrix(matrice);
		redraw = true;
		return;
	}
	if(touche=='n'){ 	//Augmentation de la taille du modèle de 10%
		cout<<"Changement dirnormale"<<endl;
		if(normaldir) normaldir = false;
		else  normaldir = true;
		redraw = true;
		return;
	}
}

void mouse(int button, int state, int x, int y){

	//Changement repère
	x = x - width/2;
	y = height/2 - y;

	switch(button){
		case GLUT_LEFT_BUTTON:
			/*	Transformer pour changer le repere */
			if (state == GLUT_DOWN){
				cout<<"Début clic gauche"<<endl;
				mouseLeft = true;
				a->mousePressed(x,y);
			}
			if (state == GLUT_UP){
				cout<<"Fin clic gauche"<<endl;
				mouseLeft = false;
				a->mouseReleased(x,y);
				redraw = true;
			}
			break;
		case GLUT_RIGHT_BUTTON:
			if( state == GLUT_DOWN){
				mouseRight = true;
				cout<<"Début clic droit"<<endl;
			}
			if (state == GLUT_UP){
				mouseRight = false;
				cout<<"Fin clic droit"<<endl;
			}
			break;
		case GLUT_MIDDLE_BUTTON:
			if( state == GLUT_DOWN){
				mouseMiddle = true;
				cout<<"Début clic milieu"<<endl;
			}
			if (state == GLUT_UP){
				mouseMiddle = false;
				cout<<"Fin clic milieu"<<endl;
			}
			break;
	}
}

void motion(int x, int y){
	//Changement repère
	x = ( x - width/2) ;
	y = (height/2 - y) ;

	if(mouseLeft){
		a->mouseDragged(x,y);
		redraw = true;
	}
}

void draw(){
	cout<<"Draw"<<endl;
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
	recalc_texture();
	bind_texture();


	drawModelFaceNormal(mt);


	glFlush();
}

/*
void drawModelPoint(model *m){
	glBegin(GL_POINTS);
	for(vector<double> p = m->beginPoint();m->hasNextPoint();p = m->nextPoint()){
		glVertex2d(p.x,p.y);
	}
	glEnd();
}

void drawModelLine(model *m){
	vector<double> p1,p2,p3;

	glBegin(GL_POINTS);

	//Recup des trois points de chaque node et affichage des lignes
	for(m->beginNode();m->hasNextNode();m->nextNode()){
		p1 = m->beginPointByNode();
		p2 = m->nextPointByNode();
		p3 = m->nextPointByNode();
		drawLine(p1.x,p1.y,p2.x,p2.y);
		drawLine(p1.x,p1.y,p3.x,p3.y);
		drawLine(p2.x,p2.y,p3.x,p3.y);
	}

	glEnd();
}

void drawModelLineNormal(model *m){
	vector<double> norm,p1,p2,p3;

	glBegin(GL_POINTS);

	//Recup des trois points de chaque node et affichage des lignes
	for(m->beginNode();m->hasNextNode();m->nextNode()){
		norm = m->getNormal();
		//norm.print();
		if(norm.z<0.){//On trace si la face regarde l'écran (z<0, z diminue en allant vers la caméra m'voyez ?)
			p1 = m->beginPointByNode();
			p2 = m->nextPointByNode();
			p3 = m->nextPointByNode();
			drawLine(p1.x,p1.y,p2.x,p2.y);
			drawLine(p1.x,p1.y,p3.x,p3.y);
			drawLine(p2.x,p2.y,p3.x,p3.y);
		}
	}

	glEnd();
}

void drawModelFaceNormal(model *m){
	vector<double> norm,p1,p2,p3;


	//Recup des trois points de chaque node et affichage des lignes
	for(m->beginNode();m->hasNextNode();m->nextNode()){
		norm = m->getNormal();
		//norm.print();
		if(norm.z<0.){//On trace si la face regarde l'écran (z<0, z diminue en allant vers la caméra m'voyez ?)
			p1 = m->beginPointByNode();
			p2 = m->nextPointByNode();
			p3 = m->nextPointByNode();

			glBegin(GL_TRIANGLES);

			glColor3f(1.0f,0.0f,0.0f);
			glVertex3d(p1.x,p1.y,p1.z);

			glColor3f(0.0f,1.0f,0.0f);
			glVertex3d(p2.x,p2.y,p2.z);

			glColor3f(0.0f,0.0f,1.0f);
			glVertex3d(p3.x,p3.y,p3.z);
			glEnd();
		}
	}

}

void drawModelPoint(modelTextured *m){
	glBegin(GL_POINTS);

	//Recup des trois points de chaque node et affichage des lignes
	for(vector<double> p = m->beginPoint();m->hasNextPoint();p = m->nextPoint()){
		//p.print();
		glVertex2d(p.x,p.y);
	}

	glEnd();
}

void drawModelLine(modelTextured *m){
	vector<double> p1,p2,p3;

	glBegin(GL_POINTS);

	//Recup des trois points de chaque node et affichage des lignes
	for(m->beginNode();m->hasNextNode();m->nextNode()){
		p1 = m->beginPointByNode();
		p2 = m->nextPointByNode();
		p3 = m->nextPointByNode();
		drawLine(p1.x,p1.y,p2.x,p2.y);
		drawLine(p1.x,p1.y,p3.x,p3.y);
		drawLine(p2.x,p2.y,p3.x,p3.y);
	}

	glEnd();
}

void drawModelLineNormal(modelTextured *m){
	vector<double> norm,p1,p2,p3;

	glBegin(GL_POINTS);

	//Recup des trois points de chaque node et affichage des lignes
	for(m->beginNode();m->hasNextNode();m->nextNode()){
		norm = m->getNormal();
		bool ok;
		if(normaldir){
			ok = norm.z<0.;
		}
		else{
			ok = norm.z>0.;
		}
		if(ok){//On trace si la face regarde l'écran (z<0, z diminue en allant vers la caméra m'voyez ?)
			p1 = m->beginPointByNode();
			p2 = m->nextPointByNode();
			p3 = m->nextPointByNode();
			drawLine(p1.x,p1.y,p2.x,p2.y);
			drawLine(p1.x,p1.y,p3.x,p3.y);
			drawLine(p2.x,p2.y,p3.x,p3.y);
		}
	}

	glEnd();
}
*/

//Ajouter les textures
void drawModelFaceNormal(modelTextured *m) {
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, tex_name);
	glBegin(GL_QUADS);
	glTexCoord2f(0.0, 0.0); glVertex2f(-1.0,  1.0);
	glTexCoord2f(1.0, 0.0); glVertex2f( 1.0,  1.0);
	glTexCoord2f(1.0, 1.0); glVertex2f( 1.0, -1.0);
	glTexCoord2f(0.0, 1.0); glVertex2f(-1.0, -1.0);
	glEnd();
	glDisable(GL_TEXTURE_2D);
	return;
}
/*

void drawLine(int x1,int y1,int x2,int y2) {
	int dx,dy,i,xinc,yinc,cumul,x,y ;

	//x,y de départ
	x = x1 ;
	y = y1 ;

	//Distance x et y
	dx = x2 - x1 ;
	dy = y2 - y1 ;

	//Si les différences sont négatives, on parcourt la dimension à l'envers
	xinc = ( dx > 0 ) ? 1 : -1 ;
	yinc = ( dy > 0 ) ? 1 : -1 ;

	//Récupération de la valeur absolue
	dx = abs(dx) ;
	dy = abs(dy) ;

	//Affichage du premier pixel point de départ
	glVertex2d(x,y);

	//Test de l'angle de la droite si dx<dy, on applique le traitement de x à y et vice-versa
	if ( dx > dy ) {
		cumul = dx / 2 ;
		for ( i = 1 ; i <= dx ; i++ ) {
			x += xinc ;
			cumul += dy ;
			if (cumul >= dx) {
				cumul -= dx ;
				y += yinc ;
			}
			glVertex2d(x,y) ;
		}
	}
	else {
		cumul = dy / 2 ;
		for ( i = 1 ; i <= dy ; i++ ) {
			y += yinc ;
			cumul += dx ;
			if ( cumul >= dy ) {
				cumul -= dy ;
				x += xinc ;
			}
			glVertex2d(x,y) ;
		}
	}
}

void drawFace(vector<int> p1,vector<int> p2,vector<int> p3){

vector<int> min,between,max;
//point max y, point min y, point entre les deux
//calculer x1 x2 pour y de maxy à entre
//calculer x1 x2 pour y de entre à min

//calculer x1, x2 en ajoutant la différence (calculer l'équation de droite)

}

void drawHLine(const int &x1,const int &x2,const int &y){
	int x;
	if(y>0 || y<(int)height) return;		//si y hors de l'écran -> skip
	if(x1<x2){								//allumage de toute la ligne
		for(x=x1;x<x2;x++){
			glVertex2d(x,y);
		}
	}
	else{
		for(x=x2;x<x1;x++){
			glVertex2d(x,y);
		}
	}
}
*/
